const { Controller } = require('egg');
const AdmZip = require('adm-zip')
const fs = require('fs')
const qiniu = require('qiniu')
const axios = require('axios')
const {Solar, Lunar, HolidayUtil} = require('lunar-javascript');
const { totalmem } = require('os');
const { rand } = require('../utils/rand');

class XueCalendarController extends Controller {
    urlPrefix = 'https://image-space.thebestxt.cc/'

    errorCodeMap = {
        // 普通错误
        500: '',
        // 未登录
        501: '请登录',
        // 用户不存在
        502: '请先登录',
        // 未绑定邀请码
        503: '请先填写邀请码',
        // 已经填写，不需要重复填写邀请码
        504: '已被邀请，请进入'
    }
    async index() {
        const { ctx } = this

        // console.log('ctx.query', ctx.query, this.ctx.state.clientIp, ctx.url)
        const date = ctx.query.date ? ctx.query.date : (() => {
            const d = new Date(`2025/01/01`)
            const month = (d.getMonth() - 0 + 1).toString()
            const date = d.getDate().toString()
            return [month, date].join('.')
        })()

        const mac = new qiniu.auth.digest.Mac(this.app.config.qiniu.ak, this.app.config.qiniu.sk)
        const config = new qiniu.conf.Config()
        config.useHttpsDomain = true;
        const bucketManager = new qiniu.rs.BucketManager(mac, config);
        const list = await bucketManager.listPrefix('xt-image-space', {
            prefix: `xue_calendar/${date} `
        })

        await this.app.mysql.insert('xue_calendar_log', {
            ip: this.ctx.state.clientIp,
            url: ctx.url,
            create_time: Date.parse(new Date())
        })
        await ctx.render('xueCalendar/index.html', { list: JSON.stringify(list.data.items) })
    }

    async dashboard() {
        const { ctx } = this;

        const mac = new qiniu.auth.digest.Mac(this.app.config.qiniu.ak, this.app.config.qiniu.sk)
        const config = new qiniu.conf.Config()
        config.useHttpsDomain = true;
        const bucketManager = new qiniu.rs.BucketManager(mac, config);
        const list = await bucketManager.listPrefix('xt-image-space', {
            prefix: 'xue_calendar/'
        })

        const logCount = await this.app.mysql.count('xue_calendar_log')
        const log = await this.app.mysql.select('xue_calendar_log', {
            limit: 100,
            orders: [['create_time', 'desc']]
        })

        // console.log(logCount, log);
        

        await ctx.render('xueCalendar/dashboard.html', {
            list: JSON.stringify(list.data.items.filter(v => v.key !== 'xue_calendar/')),
            logCount,
            log: JSON.stringify(log),
        })
    }

    async upload() {
        const { ctx } = this
        const { ymd = null, category = null } = ctx.query

        // console.log('ymd, category', ymd, category)

        const files = ctx.request.files
        let filename = files[0].filename
        if (ymd !== null && category !== null) {
            filename = [
                ymd.replaceAll('/', '-'),
                category,
                rand(100, 999),
                new Date().getTime()
            ].join('-') + '.' + files[0].filename.split('.').at(-1)
        }
        // console.log(ctx.request.files)
        
        const mac = new qiniu.auth.digest.Mac(this.app.config.qiniu.ak, this.app.config.qiniu.sk)
        const options = {
            scope: `xt-image-space:xue_calendar/${filename}`,
            force: true
        };
        const putPolicy = new qiniu.rs.PutPolicy(options)
        const uploadToken = putPolicy.uploadToken(mac)
        const config = new qiniu.conf.Config()
        
        async function _upload() {
            const formUploader = new qiniu.form_up.FormUploader(config)
            const putExtra = new qiniu.form_up.PutExtra()
            const key = 'xue_calendar/' + filename
            // 文件上传
            return new Promise((resolve, reject) => {
                formUploader.putFile(uploadToken, key, files[0].filepath, putExtra)
                    .then(({ data, resp }) => {
                        if (resp.statusCode === 200) {
                            resolve({
                                res: 'ok',
                                data
                            });
                        } else {
                            resolve({
                                res: 'err',
                                code: resp.statusCode,
                                data
                            })
                        }
                    })
                    .catch(err => {
                        resolve({
                            res: 'err',
                            err
                        })
                    })
            })
        }

        try {
            const uploadRes = await _upload()

            ctx.body = {
                code: 200,
                msg: '上传完毕',
                data: {
                    res: 'ok',
                    data: uploadRes
                }
            }
        } catch (e) {
            // console.log(e)
            
            ctx.body = {
                code: 500,
                msg: '上传失败',
                data: e
            }
        }
    }

    async del() {
        const { ctx } = this
        const key = ctx.request.query.key
        const mac = new qiniu.auth.digest.Mac(this.app.config.qiniu.ak, this.app.config.qiniu.sk)
        const config = new qiniu.conf.Config()
        config.useHttpsDomain = true;
        const bucketManager = new qiniu.rs.BucketManager(mac, config);
        if (!key) {
            const listRes = await bucketManager.listPrefix('xt-image-space', {
                prefix: 'xue_calendar/'
            })
            const list = listRes.data.items

            const deleteOperations = list.map(v => qiniu.rs.deleteOp('xt-image-space', v.key))
            try {
                const { data, resp } = await bucketManager.batch(deleteOperations)
                // console.log('data, resp', data, resp)
                ctx.body = {
                    code: 200,
                    msg: '',
                    data: {
                        data,
                        resp
                    }
                }
            } catch (e) {
                console.error(e)
                ctx.body = {
                    code: 500,
                    msg: '删除错误 2',
                    data: {e}
                }
            }
            
        } else {
            try {
                const { data, resp } = await bucketManager.delete('xt-image-space', key)
                if (resp.statusCode === 200) {
                    ctx.body = {
                        code: 200,
                        msg: 'ok',
                        data: {...data}
                    }
                } else {
                    ctx.body = {
                        code: 500,
                        msg: '删除错误',
                        data: {
                            resp,
                            data
                        }
                    }
                }
            } catch (e) {
                console.error(e)
                ctx.body = {
                    code: 500,
                    msg: '删除错误 2',
                    data: {e}
                }
            }
        }
    }

    async getLunar() {
        const fromTimestamp = Date.parse(new Date('2025/01/01'))
        const toTimestamp = Date.parse(new Date('2025/12/31'))
        
        let res = []
        for(let curTimestamp = fromTimestamp; curTimestamp <= toTimestamp; curTimestamp += 24 * 60 * 60 * 1000) {
            const dd = new Date(curTimestamp)
            const d = Lunar.fromDate(new Date(curTimestamp))
            res.push({
                year: dd.getFullYear(),
                month: dd.getMonth() - 0 + 1,
                date: dd.getDate(),
                lunar: d.toString(),
                lunarYearCn: d.getYearInChinese(),
                lunarMonthCn: d.getMonthInChinese(),
                lunarDayCn: d.getDayInChinese(),
                festivals: d.getFestivals(),
                jieQi: d.getJieQi()
            })
        }
        
        this.ctx.body = {
            code: 200,
            msg: 'ok',
            data: {
                count: res.length,
                list: res
            }
        }
    }

    async addCodes() {
        const codes = this.ctx.request.body.codes
        const insertRes = await this.app.mysql.insert('xue_calendar_code', codes.split(',').map(v => ({
            code: v,
            create_time: Date.parse(new Date()),
        })))
        this.ctx.body = {
            code: 200,
            msg: 'ok',
            data: {
                insertRes,
                codes: codes.split(',')
            }
        }
    }

    async getCodes() {
        const { code, is_used } = this.ctx.request.query
        
        let where = []
        if (is_used && is_used !== 0) {
            where.push(is_used == 1 ? 'openid <> \'\'' : 'openid = \'\'')
        }
        if (code) {
            where.push(`code like '%${code}%'`)
        }
        const whereStr = where.length > 0 ? ' AND (' + where.join(' AND ') + ')' : ''
        
        let sql = `select * from xue_calendar_code where code <> \'\' AND valid = 1 ${whereStr} order by create_time desc;`
        const res = await this.app.mysql.query(sql)
        this.ctx.body = {
            code: 200,
            msg: 'ok',
            data: {
                count: res.length,
                list: res
            }
        }
    }

    async _getList(ymd = null, category = null) {
        if (ymd === null || category === null) {
            return []
        }
        const mac = new qiniu.auth.digest.Mac(this.app.config.qiniu.ak, this.app.config.qiniu.sk)
        const config = new qiniu.conf.Config()
        config.useHttpsDomain = true;
        const bucketManager = new qiniu.rs.BucketManager(mac, config);
        // console.log(`xue_calendar/${ymd}-${category}`);
        
        const list = await bucketManager.listPrefix('xt-image-space', {
            prefix: `xue_calendar/${ymd.replaceAll('/', '-')}-${category}`
        })

        // console.log('qiniu.list', list)
        
        return list.data.items
    }
    async getList() {
        const { ctx } = this
        const { ymd = null, category = null } = ctx.query

        // console.log('ctx.query', ctx.query)

        const list = await this._getList(ymd, category)
        // console.log('getList.list', list);
        
        
        this.ctx.body = {
            code: 200,
            msg: 'ok',
            data: {
                count: list.length,
                list
            }
        }
    }

    async wxLogin() {
        const { jscode } = this.ctx.query
        const { appid, secret } = this.app.config.xue_mp
        const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appid}&secret=${secret}&js_code=${jscode}&grant_type=authorization_code`
        const requestRes = await axios.get(url)

        // console.log('requestRes', requestRes.data)
        const { openid } = requestRes.data
        
        const getUser = await this.app.mysql.get('xue_calendar_user', {
            openid
        })

        let insertUser = null
        let updateUser = null
        if (!getUser) {
            insertUser = await this.app.mysql.insert('xue_calendar_user', {
                openid,
                create_time: new Date().getTime(),
                last_login_time: new Date().getTime()
            })
        } else {
            updateUser = await this.app.mysql.update('xue_calendar_user', {
                last_login_time: new Date().getTime()
            }, {
                where: {id: getUser.id}
            })
        }

        this.mpLog({ type: 'login', value: JSON.stringify({openid}) })
        this.ctx.body = {
            code: 200,
            msg: 'ok',
            data: {
                getUser,
                insertUser,
                updateUser,
                openid
            }
        }
    }

    async bindCode() {
        const { openid, code } = this.ctx.request.body
        if (!openid) {
            this.ctx.body = {
                code: 501,
                msg: this.errorCodeMap[501],
                data: {}
            }
            return
        }
        if (!code) {
            this.ctx.body = {
                code: 500,
                msg: '请输入邀请码'
            }
            return
        }

        const getUser = await this.app.mysql.get('xue_calendar_user', {
            openid
        })
        if (!getUser) {
            this.ctx.body = {
                code: 502,
                msg: this.errorCodeMap[502]
            }
            return
        }
        if (getUser && getUser.code) {
            this.ctx.body = {
                code: 504,
                msg: this.errorCodeMap[504],
                data: {}
            }
            return
        }

        let getCode = null
        if (code !== 'unlimited-invite-code') {
            getCode = await this.app.mysql.get('xue_calendar_code', {
                code,
                valid: 1
            })
            
            if (!getCode) {
                this.ctx.body = {
                    code: 500,
                    msg: '邀请码不存在',
                    data: {}
                }
                return
            }
            if (getCode && getCode.openid && getCode.openid !== openid) {
                this.ctx.body = {
                    code: 500,
                    msg: '邀请码已失效'
                }
                return
            }
        }

        let updateUserData = {
            code,
            code_time: new Date().getTime()
        }
        if (getCode && getCode.welcome_name) {
            updateUserData.welcome_name = getCode.welcome_name
        }
        const updateUser = await this.app.mysql.update('xue_calendar_user', updateUserData, {
            where: { openid }
        })
        const updateCode = await this.app.mysql.update('xue_calendar_code', {
            openid,
            use_time: new Date().getTime()
        }, {
            where: { code }
        })

        this.mpLog({ type: 'bind-code', value: JSON.stringify({openid, code}) })
        this.ctx.body = {
            code: 200,
            msg: 'ok',
            data: {
                getCode,
                updateUser,
                updateCode,
                code,
                openid
            }
        }
    }

    async mpGetImageList() {
        const { ctx } = this
        const { openid = null, ymd = null, category = null } = ctx.query
        // console.log('ctx.query', ctx.query)

        if (!openid) {
            this.ctx.body = {
                code: 501,
                msg: this.errorCodeMap[501],
                data: {}
            }
            return
        }
        const checkUser = await this.app.mysql.get('xue_calendar_user', { openid })

        if (!checkUser) {
            this.ctx.body = {
                code: 502,
                msg: this.errorCodeMap[502],
                data: {}
            }
            return
        }
        if (checkUser && !checkUser.code) {
            this.ctx.body = {
                code: 503,
                msg: this.errorCodeMap[503],
                data: {}
            }
            return
        }

        const _list = await this._getList(ymd, '')
        // console.log('_list', _list)
        
        const list = _list.reduce((pre, cur) => {
            const category = cur.key.split('/').at(-1).split('-').at(3) - 0
            if ([0, 1, 2, 3].includes(category)) {
                pre[category].push({
                    ...cur,
                    imgUrl: `${this.urlPrefix}${cur.key}`
                })
            }
            return pre
        }, [[], [], [], []])
        
        this.mpLog({ type: 'get-image-list', value: JSON.stringify({openid, ymd}) })

        this.ctx.body = {
            code: 200,
            msg: 'ok',
            data: {
                count: list.map(v => v.length),
                list
            }
        }
    }
    async mpLog({type, value}) {
        return await this.app.mysql.insert('xue_calendar_mp_log', {
            type,
            value,
            create_time: new Date().getTime(),
            ip: this.ctx.state.clientIp,
        })
    }
    async userList() {
        const { openid = null, welcome_name = null, comment = null, code = null, is_invited = null } = this.ctx.request.query

        let whereArr = ['1 = 1']

        if (openid !== null) {
            whereArr.push(`openid like '%${openid}%'`)
        }
        if (welcome_name !== null) {
            whereArr.push(`welcome_name like '%${welcome_name}%'`)
        }
        if (comment !== null) {
            whereArr.push(`comment like '%${comment}%'`)
        }
        if (code !== null) {
            whereArr.push(`code like '%${code}%'`)
        }
        if (is_invited !== null) {
            switch (is_invited) {
                case '1': whereArr.push(`code <> '' AND code_time <> ''`); break;
                case '-1': whereArr.push(`code == '' AND code_time == ''`); break;
            }
        }

        const sql = `select * from xue_calendar_user where ${whereArr.join(' AND ')} order by last_login_time DESC, create_time DESC;`
        const userList = await this.app.mysql.query(sql)

        this.ctx.body = {
            code: 200,
            msg: 'ok',
            data: {
                count: userList.length,
                list: userList
            }
        }
    }
    async logList() {
        const list = await this.app.mysql.select('xue_calendar_mp_log', {
            orders: [
                ['create_time', 'DESC']
            ],
            limit: 100
        })

        this.ctx.body = {
            code: 200,
            msg: 'ok',
            data: {
                count: list.length,
                list
            }
        }
    }

    async statistic() {
        const userTotal = await this.app.mysql.count('xue_calendar_user')
        const userInvited = await this.app.mysql.query('select count(id) as c from xue_calendar_user where code <> \'\' and code_time <> \'\';')
        const codeTotal = await this.app.mysql.count('xue_calendar_code', { valid: 1 })
        const codeUsed = await this.app.mysql.query(`select count(id) as c from xue_calendar_code where openid <> '' and use_time <> '' and valid = 1;`)

        const now = new Date().getTime()
        const _recently7DayLog = await this.app.mysql.query('select * from xue_calendar_mp_log;')
        // const _recently7DayLog = await this.app.mysql.query('select * from xue_calendar_mp_log where create_time BETWEEN ? and ?;', [now - 7 * 86400000, now])
        const recently7DayLog = _recently7DayLog.reduce((pre, cur) => {
            const d = new Date(cur.create_time - 0)
            const year = d.getFullYear()
            const _month = d.getMonth() - 0 + 1
            const month = _month < 10 ? `0${_month}` : _month
            const _date = d.getDate()
            const date = _date < 10 ? `0${_date}` : _date
            const ymd = `${year}-${month}-${date}`

            const findYmd = pre.find(v => v.ymd === ymd)
            if (findYmd) {
                findYmd.c ++
            } else {
                pre.push({ymd, c: 1, ymdNum: ymd.replaceAll('-', '') - 0})
            }

            return pre
        }, []).sort((a, b) => a.ymdNum - b.ymdNum)
        const _recently7DayUser = await this.app.mysql.query('select * from xue_calendar_user where create_time BETWEEN ? and ?;', [now - 7 * 86400000, now])
        const recently7DayUser = _recently7DayUser.reduce((pre, cur) => {
            const d = new Date(cur.create_time - 0)
            const year = d.getFullYear()
            const _month = d.getMonth() - 0 + 1
            const month = _month < 10 ? `0${_month}` : _month
            const _date = d.getDate()
            const date = _date < 10 ? `0${_date}` : _date
            const ymd = `${year}-${month}-${date}`

            const findYmd = pre.find(v => v.ymd === ymd)
            if (findYmd) {
                findYmd.c ++
            } else {
                pre.push({ymd, c: 1, ymdNum: ymd.replaceAll('-', '') - 0})
            }

            return pre
        }, []).sort((a, b) => a.ymdNum - b.ymdNum)
        
        // const _ymdGroupStatistic = await this.app.mysql.query(`select count(id) as c, FROM_UNIXTIME(CAST(create_time AS UNSIGNED) / 1000, '%Y-%m-%d') as ymd from xue_calendar_mp_log where type = 'get-image-list' group by ymd;`)
        // const ymdGroupStatistic = _ymdGroupStatistic.map(v => ({...v, ymdNum: v.ymd.replaceAll('-', '') - 0})).sort((a, b) => a.ymdNum - b.ymdNum)
        const _ymdGroupStatistic = await this.app.mysql.query(`select * from xue_calendar_mp_log where type = 'get-image-list';`)
        const ymdGroupStatistic = _ymdGroupStatistic.reduce((pre, cur) => {
            const value = JSON.parse(cur.value)
            const ymd = value.ymd.replaceAll('/', '-')

            // console.log('pre:', pre)
            
            const findYmd = pre.find(v => v.ymd === ymd)
            if (findYmd) {
                findYmd.c ++
            } else {
                pre.push({ymd, c: 1, ymdNum: ymd.replaceAll('-', '') - 0})
            }

            return pre
        }, []).sort((a, b) => a.ymdNum - b.ymdNum)

        this.ctx.body = {
            code: 200,
            msg: 'ok',
            data: {
                userTotal,
                userInvited: userInvited[0]['c'],
                codeTotal,
                codeUsed: codeUsed[0]['c'],
                recently7DayLog,
                recently7DayUser,
                ymdGroupStatistic
            }
        }
    }

    async recCode() {
        let { code } = this.ctx.request.body
        
        if (!code) {
            this.ctx.body = {
                code: 500,
                msg: '缺少参数 code',
                data: {}
            }
            return
        }
        let getCode = null
        let updateCode = null
        if (code !== 'unlimited-invite-code') {
            getCode = await this.app.mysql.get('xue_calendar_code', {
                code
            })
            if (!getCode) {
                this.ctx.body = {
                    code: 500,
                    msg: '此邀请码不存在',
                    data: {}
                }
            }
            updateCode = await this.app.mysql.update('xue_calendar_code', {
                openid: '',
                use_time: '',
                rec_time: new Date().getTime()
            }, { where: {
                code
            }})
        }
        const getUserByCode = await this.app.mysql.get('xue_calendar_user', {
            code
        })
        let updateUser = null
        if (getUserByCode) {
            updateUser = await this.app.mysql.update('xue_calendar_user', {
                code: '',
                code_time: ''
            }, { where: {
                code
            }})
        }

        this.ctx.body = {
            code: 200,
            msg: 'ok',
            data: {
                getCode,
                updateCode,
                updateUser
            }
        }
    }

    async recUserCode() {
        let { openid } = this.ctx.request.body
        const getUser = await this.app.mysql.get('xue_calendar_user', {
            openid
        })
        if (!getUser) {
            this.ctx.body = {
                code: 500,
                msg: '用户不存在',
                data: {}
            }
            return
        }
        if (getUser && !getUser['code']) {
            this.ctx.body = {
                code: 500,
                msg: '用户未绑定邀请码',
                data: {}
            }
            return
        }
        const updateUser = await this.app.mysql.update('xue_calendar_user', {
            code: '',
            code_time: ''
        }, { where: {
            openid
        }})
        const getCode = await this.app.mysql.get('xue_calendar_code', {
            openid
        })
        let updateCode = null
        if (getCode) {
            updateCode = await this.app.mysql.update('xue_calendar_code', {
                openid: '',
                use_time: '',
                rec_time: new Date().getTime()
            }, { where: {
                openid
            }})
        }

        this.ctx.body = {
            code: 200,
            msg: 'ok',
            data: {
                getUser,
                updateUser,
                getCode,
                updateCode
            }
        }
    }

    async delCode() {
        const { code } = this.ctx.request.body
        try {
            const recCode = this.recCode.call(this)
            const delCode = this.app.mysql.update('xue_calendar_code', {
                valid: 0,
                del_time: new Date().getTime()
            }, { where: { code } })
            
            this.ctx.body = {
                code: 200,
                msg: 'ok',
                data: {
                    recCode,
                    delCode
                }
            }
        } catch (e) {
            this.ctx.body = {
                code: 500,
                msg: e.getMessage(),
                data: {}
            }
            return
        }
    }

    async xueMpConfig() {
        this.ctx.body = {
            code: 200,
            msg: 'ok',
            data: this.app.config.xue_mp_config
        }
    }

    async updateUser() {
        const { openid = null, welcome_name = null, comment = null } = this.ctx.request.body
        if (openid === null) {
            this.ctx.body = {
                code: 500,
                msg: '请选择用户',
                data: {}
            }
            return
        }
        let updateData = {}
        if (welcome_name !== null) {
            updateData['welcome_name'] = welcome_name
        }
        if (comment !== null) {
            updateData['comment'] = comment
        }

        if (Object.keys(updateData).length > 0) {
            updateData['update_time'] = new Date().getTime()
        }

        const updateUser = await this.app.mysql.update('xue_calendar_user', updateData, { where: { openid } })
        this.ctx.body = {
            code: 200,
            msg: 'ok',
            data: {
                updateUser
            }
        }
    }

    async getMpAccessToken() {
        let at = await this.app.redis.get('xue_calendar_mp_access_token')
        if (!at) {
            const { appid, secret } = this.app.config.xue_mp
            const atRes = await axios.get(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`)
            const { access_token } = atRes.data
            await this.app.redis.set('xue_calendar_mp_access_token', access_token, 'EX', 7000)
            at = access_token
        }
        return at
    }
    async genQr() {
        const { code = null, welcome_name = null } = this.ctx.request.query
        if (code === null) {
            this.ctx.body = {
                code: 500,
                msg: '缺少参数 code',
                data: {}
            }
            return
        }
        let updateCode = null
        if (welcome_name !== null) {
            updateCode = await this.app.mysql.update('xue_calendar_code', {
                welcome_name
            }, { where: { code } })
        }
        const genCodeUrl = `https://api.weixin.qq.com/wxa/getwxacode?access_token=${await this.getMpAccessToken()}`
        // console.log('genCodeUrl:', genCodeUrl)
        
        const genCodeRes = await axios.post(genCodeUrl, {
            path: `/pages/login/login?invite_code=${code}`,
            env_version: 'release', // release
            width: 160
        }, {
            responseType: 'arraybuffer'
        })

        this.ctx.body = {
            code: 200,
            msg: 'ok',
            data: {
                updateCode,
                genCodeRes: genCodeRes.data
            }
        }
    }
}

module.exports = XueCalendarController;
